using System;
using System.IO;
using System.Xml.Serialization;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System.Collections;
using System.Collections.Generic;

namespace com.exceptionalsoftware.spacetime.KML
{
	[Serializable()]
    [XmlType(TypeName = "KmlType")]
    [XmlRoot(ElementName = "kml")]
	public class KMLDocument
	{
        private NetworkLinkControl nlcField;
        private Feature featureField;
        private static string versionField = "2.1";
        private static string name = "Default";
        public KMLDocument()
		{
            // Do nothing.
		}

        public KMLDocument(Feature f)
        {
            this.featureField = f;
        }

        public static string Version
        {
            get { return versionField; }
            set { versionField = value; }
        }

        public static string Name
        {
            get { return name; }
            set { name = value; }
        }
        private static XmlSerializer NewSerializer
        {
            get
            {
                try
                {
                    XmlSerializer s = new XmlSerializer(typeof(KMLDocument));
                    s.UnknownElement += new XmlElementEventHandler(HandleUnknownElement);
                    s.UnknownAttribute += new XmlAttributeEventHandler(HandleUnknownAttribute);

                    return s;
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    return null;
                }
            }
        }

        static void HandleUnknownAttribute(object sender, XmlAttributeEventArgs e)
        {
            Console.WriteLine(e.Attr.Name);
            Console.WriteLine(e.Attr.OuterXml);
            Console.WriteLine(e.Attr.InnerXml);
        }

        static void HandleUnknownElement(object sender, XmlElementEventArgs e)
        {
            Console.WriteLine("Element Name: " + e.Element.Name);
            Console.WriteLine("Outer XML: " + e.Element.OuterXml);
            Console.WriteLine("Expected Elements: " + e.ExpectedElements);
            Console.WriteLine("Class: " + e.ObjectBeingDeserialized.GetType().ToString());
            Console.WriteLine();
        }

        [XmlElement(ElementName = "Folder", Type = typeof(Folder))]
        [XmlElement(ElementName = "Document", Type = typeof(Document))]
        [XmlElement(ElementName = "Placemark", Type = typeof(Placemark))]
        [XmlElement(ElementName = "NetworkLink", Type = typeof(NetworkLink))]
        [XmlElement(ElementName = "GroundOverlay", Type = typeof(GroundOverlay))]
        [XmlElement(ElementName = "ScreenOverlay", Type = typeof(ScreenOverlay))]
        [XmlElement(ElementName = "PhotoOverlay", Type = typeof(PhotoOverlay))]
        public Feature Feature
        {
            get { return this.featureField; }
            set { this.featureField = value; }
        }

        [XmlElement(ElementName = "NetworkLinkControl", Type = typeof(NetworkLinkControl))]
        public NetworkLinkControl NetworkLinkControl
        {
            get { return this.nlcField; }
            set { this.nlcField = value; }
        }

        public void WriteToFile(string path)
        {
            StreamWriter kmlStream = new StreamWriter(path);
            try
            {
                this.Write(kmlStream);
            }
            catch
            {
            }
            finally
            {
                kmlStream.Close();
            }
        }

        public void Write(Stream kmlStream)
        {
            string kmlString = WriteToString();
            System.Text.ASCIIEncoding asciiEncoding = new System.Text.ASCIIEncoding();
            byte[] kmlBytes = asciiEncoding.GetBytes(kmlString);
            kmlStream.Write(kmlBytes, 0, kmlBytes.Length);
        }
        
        public void Write(TextWriter kmlWriter)
        {
            string kmlString = WriteToString();
            kmlWriter.Write(kmlString);
        }

        public string WriteToString()
        {
            string kmlString = "";
            try
            {
                MemoryStream sw = new MemoryStream();
                new XmlSerializer(typeof(KMLDocument)).Serialize(sw, this);
                StreamReader sr = new StreamReader(sw);
                sr.BaseStream.Position = 0;
                kmlString = sr.ReadToEnd();
                if (kmlString.Contains("<kml"))
                {
                    try
                    {
                        int startNDX = kmlString.IndexOf("<kml");
                        int endNDX = kmlString.IndexOf(">", startNDX) - startNDX + 1;
                        kmlString = kmlString.Replace(kmlString.Substring(startNDX, endNDX), "<kml xmlns=\"http://earth.google.com/kml/" + versionField + "\">");
                    }
                    catch
                    {
                    }
                }
                sr.Close();
            }
            catch
            {
            }
            return kmlString;
        }

        public static KMLDocument[] ReadKMZorZip(Stream kmz_zipStream)
        {
            try
            {
                kmz_zipStream.Position = 0;
            }
            catch
            {
            }
            try
            {
                using (ZipInputStream s = new ZipInputStream(kmz_zipStream))
                {

                    ZipEntry theEntry;
                    List<KMLDocument> layers = new List<KMLDocument>();
                    while ((theEntry = s.GetNextEntry()) != null)
                    {
                        try
                        {
                            if (theEntry.Name.ToLower().EndsWith("kml"))
                            {
                                using (MemoryStream memStream = new MemoryStream())
                                {
                                    int size = 2048;
                                    byte[] data = new byte[2048];
                                    while (true)
                                    {
                                        size = s.Read(data, 0, data.Length);
                                        if (size > 0)
                                        {
                                            memStream.Write(data, 0, size);
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }

                                    KMLDocument lyr = Read(memStream, theEntry.Name);
                                    layers.Add(lyr);
                                }
                            }
                            else if (theEntry.Name.ToLower().EndsWith("kmz") || theEntry.Name.ToLower().EndsWith("zip"))
                            {
                                using (MemoryStream memStream = new MemoryStream())
                                {
                                    int size = 2048;
                                    byte[] data = new byte[2048];
                                    while (true)
                                    {
                                        size = s.Read(data, 0, data.Length);
                                        if (size > 0)
                                        {
                                            memStream.Write(data, 0, size);
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }
                                    try
                                    {
                                        memStream.Position = 0;
                                    }
                                    catch
                                    {
                                    }

                                    KMLDocument[] lyrs = ReadKMZorZip(memStream);
                                    layers.AddRange(lyrs);
                                }
                            }
                        }
                        catch
                        {
                        }
                    }
                    return layers.ToArray();
                }
            }
            catch
            {
            }
            return null;
        }

        public static KMLDocument[] ReadKMZorZipFromFile(string kmz_zipFilePath)
        {
            return ReadKMZorZip(File.OpenRead(kmz_zipFilePath));
        }

        public static KMLDocument ReadFromFile(string path)
        {
            StreamReader kmlStream = new StreamReader(path);
            KMLDocument kmlDoc = null;
            FileInfo fi = new FileInfo(path);

            try
            {
                kmlDoc = KMLDocument.Read(kmlStream, fi.Name);
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message);
            }
            finally
            {
                kmlStream.Close();
            }

            return kmlDoc;
        }

        public static KMLDocument Read(string kmlString, string Name)
        {
            if (kmlString.Contains("<kml"))
            {
                try
                {
                    int startNDX = kmlString.IndexOf("<kml");
                    int endNDX = kmlString.IndexOf(">", startNDX) - startNDX + 1;
                    //Remove any data before or after the <kml> </kml> tags
                    //int endNDX3 = kmlString.IndexOf(@"</kml>");
                    //kmlString = kmlString.Substring(startNDX, endNDX3 + 5);
                    try
                    {
                        int startNDX2 = kmlString.Substring(startNDX, endNDX).IndexOf("/kml/") + 5;
                        int endNDX2 = kmlString.Substring(startNDX, endNDX).IndexOf("\"", startNDX2) - startNDX2;
                        versionField = kmlString.Substring(startNDX, endNDX).Substring(startNDX2, endNDX2);
                    }
                    catch
                    {
                    }
                    kmlString = kmlString.Replace(kmlString.Substring(startNDX, endNDX), "<kml>");


                }
                catch
                {
                }
            }
            try
            {
                kmlString = kmlString.Replace("></>", "/>");
            }
            catch
            {
            }
            return ReadFinal(new StringReader(kmlString), Name);
        }

        public static KMLDocument Read(Stream kmlStream, string Name)
        {
            kmlStream.Position = 0;
            StreamReader sr = new StreamReader(kmlStream);
            string kmlDoc = sr.ReadToEnd();
            return KMLDocument.Read(kmlDoc, Name);
        }

        public static KMLDocument Read(TextReader kmlReader, string Name)
        {
            string kmlDoc = kmlReader.ReadToEnd();
            return KMLDocument.Read(kmlDoc, Name);
        }

        private static KMLDocument ReadFinal(TextReader kmlReader, string Name)
        {
            if (name == null || name.Trim() == "")
                name = "KMLDoc";
            name = name.Replace(".", "_");
            name = Name;

            return (KMLDocument)(KMLDocument.NewSerializer.Deserialize(kmlReader));
        }
        
        public override string ToString()
        {
            return base.ToString();
        }

        
    }
}